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

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

Related

Unable to have an ol3 map in each angular module

I'm using openlayers3 (ol3) and angular 1.5.6 on IE Edge.
I have two modules. Each has their own controller and component. Each controller wants to have a map in the view. One view is for interactively querying data off its map. The other view is for displaying interactive query results.
Under the hood, I provide a MapFactory which returns an instance of a object, containing the said openlayers map.
PROBLEM: The one displays while the other does not.
Here's a sample of my code (some details are left out for simplicity. For example the dependency injection checks. All of this code is being called as expected.):
Module A definition
angular.module('ModuleA').controller('ModuleAController',ModuleAController);
ModuleAController.$inject = ['MapFactory'];
function ModuleAController(MapFactory){
var vm = this;
var vm.map = MapFactory.getMapInstance({
id:'module-A-map',
otherOption:true
});
}
In ModuleA's view:
<div id='module-A-map' class="map-classes"></div>
Module B definition
angular.module('ModuleB').controller('ModuleBController',ModuleBController);
ModuleBController.$inject = ['MapFactory'];
function ModuleBController(MapFactory){
var vm = this;
var vm.map = MapFactory.getMapInstance({
id:'module-B-map',
otherOption:true
});
}
In ModuleB's view:
<div id='module-B-map' class="map-classes"></div>
MapFactory's definition:
angular.module('common').factory('MapFactory',MapFactory);
MapFactory.$inject = [];
function MapFactory(){
var factory = {
getMapInstance : getMapInstance
};
return factory;
function getMapInstance(options){
return new _MapConstructor(options);
}
function _MapConstructor(options){
var _map = new ol.Map({
target : options.id,
logo : false,
view : new ol.View({...}),
layers : [some,layers,here]
});
return {
publicMethod : publicMethod
};
function publicMethod(){...}
function privateMethod(){...}
... other stuff ...
}
}
Please, let me know if any clarification is needed to answer the question.
MORE:
This issue: https://github.com/openlayers/ol3/issues/4601 might be part of the problem. I am using collapsable DIVs with bootstrap. The ModuleA is in the default displayed one, while ModuleB is hidden at first. More to come.
I wrote this up as an OL3 issue as well: https://github.com/openlayers/ol3/issues/5789
ABSTRACT ANSWER:
http://getbootstrap.com/javascript/#collapse-events
I need to add a _map.updateSize() on a show.bs.collapse or shown.bs.collapse event. Now, I need to figure out how to do that in Angular, and post it (unless somebody gets to it first).
Ah, this is in Bootstrap's collapse class. So, let's back up to the Module-B view. Each of my Module's is a panel within a Bootstrap panel accordian. The ModuleA map that displays is the default open panel (the one that has the in class). The ModuleB map is not open by default, and thus, OL3 gives the canvas a display:none in the map's div's style.
<div id="module-B-collapse" class="panel-collapse collapse" >
<div id='module-B-map' class="map-classes"></div>
....
</div>
In my ModuleBController, I simply added:
angular.element('#module-B-collapse').on('shown.bs.collapse',function(){
_map.updateSize();
});

angular-ui-tree: dropped location + catch dropped event in directive

I'm using angular-ui-tree for building a tree of items in my app.
I'm using its drag & drop feature and I need to know when & where (on what element) the dropping occurs.
For example, I drag item1, and drop it on a panel. I want the panel to display the item name. (each item has a name property). the panel is just a simple div with text inside.
I saw in the documentations that I can access the "dropped" event in my controller. But I don't understand how to change the panel content according to the dragged & dropped item.
As in documentations $callbacks (type: Object)
$callbacks is a very important property for angular-ui-tree. When some
special events trigger, the functions in $callbacks are called. The
callbacks can be passed through the directive.
you define the events in a treeOptions collection
myAppModule.controller('MyController', function($scope) {
// here you define the events in a treeOptions collection
$scope.treeOptions = {
accept: function(sourceNodeScope, destNodesScope, destIndex) {
return true;
},
dropped: function(e) {
console.log (e.source.nodeScope.$modelValue);
}
};
});
then in your tree div add callbacks="treeOptions" which you defined above in the controller
<div ui-tree callbacks="treeOptions">
<ol ui-tree-nodes ng-model="nodes">
<li ng-repeat="node in nodes" ui-tree-node>{{node.title}}</li>
</ol>
</div>
then you can access the old parent from here
e.source.nodeScope.$parentNodeScope.$modelValue
and you can access the new parent from here
e.dest.nodesScope.$parent.$modelValue
Hey guys i just found it !
$scope.treeOptions = {
dropped: function (event) {
//To catch the event after dragged
//Value of model which is moving
event.source.nodeScope.$modelValue;
//Source Parent from where we are moving model
event.source.nodeScope.$parentNodeScope.$modelValue;
//Destination Parent to where we are moving model
//Edit: Use "nodesScope" instead of "nodeScope" for dest object
event.dest.nodesScope.$nodeScope.$modelValue;
}};
Hope it works for you too:)
You access the "dropped" item like this.
$scope.elOptions = {
dropped: function(e) {
console.log (e.source.nodeScope.$modelValue);
}
};
Addional information which might be useful can be found on this issue of the project : https://github.com/angular-ui-tree/angular-ui-tree/issues/272
For example in my case, I was dragging from one tree to another one, and in this case, the dropped function must be overriden in the SOURCE tree options (and not the DESTINATION one like I initially thought).
The discussion in the related issue helped me a lot to find this out.

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

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;
})

Where do you put this kind of controller code in an angular app?

The following code is needed in 2 different controllers (at the moment, maybe more controllers later). The code works around a problem I've found in ng-grid and allows the delayed selection of a row (once the data has been loaded).
// Watch for the ngGridEventData signal and select indexToSelect from the grid in question.
// eventCount parameter is a hack to hide a bug where we get ngGridEventData spam that will cause the grid to deselect the row we just selected
function selectOnGridReady(gridOptions, indexToSelect, eventCount) {
// Capture the grid id for the grid we want, and only react to that grid being updated.
var ngGridId = gridOptions.ngGrid.gridId;
var unWatchEvent = $scope.$on('ngGridEventData', function(evt, gridId) {
if(ngGridId === gridId) {
//gridEvents.push({evt: evt, gridId:gridId});
var grid = gridOptions.ngGrid;
gridOptions.selectItem(indexToSelect, true);
grid.$viewport.scrollTop(grid.rowMap[0] * grid.config.rowHeight);
if($scope[gridOptions.data] && $scope[gridOptions.data].length) {
eventCount -= 1;
if(eventCount <= 0) {
unWatchEvent(); // Our selection has been made, we no longer need to watch this grid
}
}
}
});
}
The problem I have is where do I put this common code? It's obviously UI code, so it doesn't seem like it belongs in a service, but there is no classical inheritance scheme (that I have been able to discover) that would allow me to put it in a "base class"
Ideally, this would be part of ng-grid, and wouldn't involve such a nasty hack, but ng-grid 2.0 is closed to features and ng-grid 3.0 is who knows how far out into the future.
A further wrinkle is the $scope that I guess I would have to inject into this code if I pull it from the current controller.
Does this really belong in a service?
I would probably just put this in a service and pass $scope into it but you do have other options. You may want to take a look at this presentation as it covers different ways of organizing your code: https://docs.google.com/presentation/d/1OgABsN24ZWN6Ugng-O8SjF7t0e3liQ9UN7hKdrCr0K8/present?pli=1&ueb=true#slide=id.p
Mixins
You could put it in its own object and mix it into any controllers using angular.extend();
var ngGridUtils = {
selectOnGridReady: function(gridOptions, indexToSelect, eventCount) {
...
}
};
var myCtrl = function() {...};
angular.extend(myCtrl, ngGridUtils);
Inheritance
If you use the 'controller as' syntax for your controllers then you can treat them like classes and just use javascript inheritance.
var BaseCtrl = function() {
...
}
BaseCtrl.prototype.selectOnGridReady = function(gridOptions, indexToSelect, eventCount) {
...
};
var MyCtrl = function() {
BaseCtrl.call(this);
};
MyCtrl.prototype = Object.create(BaseCtrl.prototype);
HTML:
<div ng-controller="MyCtrl as ctrl"></div>

Leaflet markers from Backbone.js collection?

I am trying to build an application based on backbone.js and leaflet.
Users could drag the map and see markers on the map.
Markers can be selected by clicking on them. When selected they have to change their icon and the marker detailed information shown on a (not popup).
my backbone model consists of several entities:
Marker model contains
latitude, longitude
type,
title,
isSelected
Map model contains:
center of the map,
markers collection,
selected marker
anyone has any idea how i could make this kind of functionality?
how can i make leaflet markers as backbone views?
Backbone views and the leaflet object model are not a perfect fit, because the markers aren't contained within a DOM element, which is what Backbone.View.el is supposed to represent. Markers do of course have an element (accessible via marker._icon), but it doesn't exist until the marker is rendered to the map.
That said, you can represent the markers with Backbone views, you just can't use the events or any el related functionality. I've implemented similar views successfully using OpenLayers, which has the same "problem", and it works fine.
I think this is easiest to explain with code:
//MarkerView has no element
App.Views.MarkerView = Backbone.View.extend({
initialize: function(options) {
//pass map instance to the marker
this.map = options.map;
//create the marker object
this.marker = L.marker([this.model.get('longitude'), this.model.get('latitude')]);
},
render: function() {
//append marker to the map
this.marker.addTo(this.map);
//can't use events hash, because the events are bound
//to the marker, not the element. It would be possible
//to set the view's element to this.marker._icon after
//adding it to the map, but it's a bit hacky.
this.marker.on('click', this.onClick);
},
onClick: function() {
alert("click");
}
});
//MapView renders a map to the #map element
App.Views.MapView = Backbone.View.extend({
id:"#map",
render: function() {
//render map element
var map = this.map = L.map(this.$el.attr('id'))
.setView([this.model.get('centerLon'), this.model.get('centerLat') ], 13)
.addLayer(L.tileLayer(this.model.get('layerUrl'), { maxZoom: 18 }));
//render each marker
this.markerViews = this.model.get('markers').map(function(marker) {
return new App.Views.MarkerView({model:marker, map:map}).render();
});
}
});
Here's a demo on JSFiddle.

Resources