OpenLayers 3: Sync/Unsync Different Maps Events - openlayers-3

Let's say I have N different maps on the screen, I want to be able based on a condition, let s say a value coming from a checkbox or a different control to be able to sync all the map events (zoom/pan/rotation) and unsync them accordingly.
Can this be achieved in OpenLayers 3 using native code?

You can sync maps by listen to their events and setting their changes to the other maps. I wrote a working example, using this as the listener for each map's view's change:center, change:resolution and change:rotation event:
function(evt){
var type = evt.type.split(':');
if (type[0] === 'change' && type.length === 2){
var attribute = type[1];
maps.forEach(function(map){
var value = evt.target.get(attribute);
if (map.getView().get(attribute) !== value){
map.getView().set(attribute, value);
}
});
}
}

I'm working on a project with fairly heavy WMS layers and hit a problem with Alvin's solution. For the first map OL waits for any interaction to end before requesting an updated WMS layer. But for the second map it appears to treat each step triggered by the function as a separate interaction and therefore generates a separate WMS layer request at each step. In my case this was seriously affecting performance.
I've come up with a compromise solution that waits for interaction to end and then animates the second map to match the first. This is a first attempt and could probably be improved:
// attributes to synchronise between maps
var synchedAttributes = ['resolution', 'center']
// call this on moveend
var syncPanZoom = function(maps, evt){
maps.forEach(function(map){
synchedAttributes.forEach(function(attribute) {
var value = evt.target.getView().get(attribute);
if (map.olmap.getView().get(attribute) !== value){
var animateObject = {};
animateObject[attribute] = value;
animateObject.duration = 500;
map.olmap.getView().animate(animateObject)
}
});
});
};

Related

Highcharts: how to remove selected annotation (which was dynamically created)?

I have this code https://jsfiddle.net/delux123/62Lmskbx/11/ where user can draw circle, segment and arrow-segment. When each annotation is selected, there is a "delete" button available, which should normally delete the selected annotation.
But this does not works! I tried the following ways for removing them:
1 ) using thischart.currentAnnotation.destroy() will delete the annotation (works for all three types) but then all the further actions stops from working
document
.querySelectorAll('.highcharts-popup-annotations .deletebutton')[0]
.addEventListener(
'click',
function() {
thischart.currentAnnotation.destroy(); //this stops further drawing
thischart.annotationsPopupContainer.style.display = 'none';
}
);
2 ) using deletion by ID thischart.removeAnnotation(thischart.currentAnnotation.options.id) does not works, since the annotations are created dynamically and they do not have IDs assigned to them.
document
.querySelectorAll('.highcharts-popup-annotations .deletebutton')[0]
.addEventListener(
'click',
thischart.removeAnnotation(thischart.currentAnnotation.options.id); //this requires elements to have IDs (which they do not have)
thischart.annotationsPopupContainer.style.display = 'none';
}
);
For the second approach, I even tried to intersect the drawing and to assign a random string as an ID (since the reason that deletion by id does not works, is because the ID is undefined). So under navigation -> bindings I added the object:
circleAnnotation: {
start: function(e) {
var navigation = this.chart.options.navigation;
return this.chart.addAnnotation(
Highcharts.merge({
id: randomStr() //this is a method that generates random string
},
navigation
.annotationsOptions,
navigation
.bindings
.circleAnnotation
.annotationsOptions
)
);
}
}
The both approaches are not working.
You can pass a direct annotation object to the removeAnnotation method:
thischart.removeAnnotation(thischart.currentAnnotation);
Live demo: https://jsfiddle.net/BlackLabel/5ycf3s4o/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Chart#removeAnnotation

angular ui-grid selecting all under grouping

https://jsfiddle.net/4byyuqtc/1/
I'm looking to have the ui-grid select all "children" under a grouping when the grouping line is selected. In this case Kit Kat(1), Mr. Goodbar(1), Krackel(2) and ultimately selecting the actual records (the non bold lines). One would expect that when selecting a parent in a grouping all it's children would get selected as well.
Currently when selecting the 1 grouping above the actual records in the data (the non bold lines) it does select those actual records with the following code:
$scope.gridApi.selection.on.rowSelectionChanged($scope, function (rowChanged) {
console.log(rowChanged.treeLevel);
if (typeof (rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
// this is a group header
children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
console.log(children);
children.forEach(function (child) {
if (rowChanged.isSelected) {
$scope.gridApi.selection.selectRow(child.entity);
} else {
$scope.gridApi.selection.unSelectRow(child.entity);
}
});
}
});
I'm not experienced enough with ui-grid at this point to figure out how to cycle through children of the selected line and select all of them.
[EDIT]
With Paul's code below it doesn't select the groupings but it's closer. This screenshot is me selecting the first 337 record. Notice it selects that record and all the lowest child records (which is good because ultimately those are the ones that matter) but visually the grouped records (MFG and Item Desc group) aren't selected and need to be as the user won't ever open the lowest data records so they need to see the groups selected.
I checked the documentation and I don't think there's any exposed API Method. You could recursively select/deselect rows as a solution. Please try out the example below.
$scope.gridApi.selection.on.rowSelectionChanged($scope, function (rowChanged) {
console.log(rowChanged.treeLevel);
if (typeof(rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
var children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
selectChildren(children, rowChanged.isSelected);
}
});
function selectChildren(gridRows, selected) {
if (gridRows && gridRows.length > 0) {
gridRows.forEach(function (child) {
if (selected) {
$scope.gridApi.selection.selectRow(child.entity);
} else {
$scope.gridApi.selection.unSelectRow(child.entity);
}
var children = $scope.gridApi.treeBase.getRowChildren(child);
selectChildren(children, selected); //recursively select/de-select children
});
}
}
Here's a working Plunkr: http://plnkr.co/edit/XsoEUncuigj9Cad1vP5E?p=preview
Handling automatic deselection is a bit more tricky though as it seems the api doesn't handle that really well.
UPDATE
So I checked the jsFiddle you shared and managed to get it working with a slight tweak.
I modified the selectionHandler to the following:
onRegisterApi: function(gridApi) {
$scope.gridApi = gridApi;
$scope.gridApi.selection.on.rowSelectionChanged($scope, function(rowChanged) {
if (rowChanged.treeNode.parentRow) { //Added this parent row selection
rowChanged.treeNode.parentRow.setSelected(rowChanged.isSelected);
}
console.log(rowChanged.treeLevel);
if (typeof(rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
var children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
selectChildren(children, rowChanged.isSelected);
}
});
Please see this fork of your code: https://jsfiddle.net/1eg5v77w/
The downside with this is that if you select a low level entry (one without children) it will still select its parent. If you really really want this to work as well, you'll have to access the DOM and make some ugly checks.
$scope.gridApi.selection.on.rowSelectionChanged($scope, function(rowChanged, $event) {
var wasHeaderRowClicked = true;
try { //This can be written more beautifully if you used jQuery. But I would still be against it as it relies on the class of the ui-grid never changing when you update your ui-grid version.
wasHeaderRowClicked = $event
.srcElement
.parentElement
.parentElement
.parentElement
.previousElementSibling
.firstChild
.firstChild
.firstChild
.getAttribute('class') === 'ui-grid-icon-minus-squared';
} catch(err) { console.log('Couldnt determine if header row was clicked'); }
if (rowChanged.treeNode.parentRow && wasHeaderRowClicked) {
rowChanged.treeNode.parentRow.setSelected(rowChanged.isSelected);
}
console.log(rowChanged.treeLevel);
if (typeof(rowChanged.treeLevel) !== 'undefined' && rowChanged.treeLevel > -1) {
var children = $scope.gridApi.treeBase.getRowChildren(rowChanged);
selectChildren(children, rowChanged.isSelected);
}
});
Here is the fiddle: https://jsfiddle.net/Lf8p7Luk/1/
I'd also like to add, thanks to this post, that according to the UI-Grid documentation: Group header rows cannot be edited, and if using the selection feature, cannot be selected. They can, however, be exported.
So it is intentional that it's so difficult to get this to work because it's not the intended design. My recommendation would be to alter your logic to either use Tree Levels or get around the selection logic because even though my fork is currently selecting everything, you will most likely run into other issues down the road. For example: I couldn't get automatic deselection to work in the grid when you click on another group header.
If you still have the issue take a look with this..
https://github.com/angular-ui/ui-grid/issues/3911

It's necessary to remove views? In order to cleanup the Alloy controller (memory/performance)

Lets say that I've a ScrollableView with 3 Views (forms), those form views have at least 10 fields, take a look at this exemple.
index.js
$.content.add(Alloy.createController('scrollable').getView());
scrollable.js
$.scrollableView.addView(Alloy.createController('form',{
fields:[
{label:'field 1',type:'text'},
{label:'field 1',type:'date',value:'2016-06-08'},
...
]
}).getView());
$.scrollableView.cleanup = function() {
$.destroy();
$.off();
for(var i = parseInt($.scrollableView.views.length); i > 0; i--) if($.scrollableView.views[i-1]) {
if($.scrollableView.views[i-1].cleanup) $.scrollableView.views[i-1].cleanup();
$.scrollableView.views[i-1] = null;
$.scrollableView.removeView($.scrollableView.views[i-1]);
}
$ = args = null;
};
form.js
for(var i in args.fields) $.form.add(Alloy.createController('field',args.fields[i]).getView());
$.form.cleanup = function() {
$.destroy();
$.off();
for(var i in $.form.children) {
if($.form.children[i].cleanup) $.form.children[i].cleanup();
$.form.children[i] = null;
}
$.form.removeAllChildren();
$ = args = null;
};
When I'm cleaning up all the controllers, I still don't understand what it's necessary to do.
When I want to remove the ScrollableView, I run the cleanup function on every View, and it's children.
Should I run the cleanup function on all the ScrollableView views?
Should I null all the ScrollableView views?
Should I remove all the ScrollableView views?
Should I run the cleanup function on all the View children?
Should I null all the View children?
Should I remove all the View children?
UPDATE
In this case, I still need to cleanup all the fields? or setting the data to null will solve that?
form.js
var args = arguments[0],
data = {
fields:{}
};
for(var i in args.fields) {
data.fields[args.fields[i].label] = Alloy.createController('field',args.fields[i]).getView();
$.form.add(data.fields[args.fields[i].label]);
}
$.form.cleanup = function() {
$.destroy();
$.off();
//this is needed?
for(var i in data.fields) {
if(data.fields[i].cleanup) data.fields[i].cleanup();
data.fields[i] = null;
}
//this is needed?
$ = data = args = null;
};
Anyway, if my fields have an event listener added like 'change' or 'click', I must remove it in cleanup function, right?
There is no need to remove all views, the only thing you need to do to clean up memory is remove the most parent view, and all references to anything within the most parent view & the reference to the parent view.
So in your case, you only have to remove the ScrollableView and within the scrollableview you need to do $.off(). $.destroy() is only needed if you use data-binding (models/collections).
Because your child views never have a reference (variable), there is no need to remove them. It is automatically handled by Appcelerator/JavaScript and will be cleaned up with garbage collection when the time comes.
note: Garbage collection doesn't happen directly after you remove the views, so you might still have increased memory usage. Both JavaScript and the native platform have their own garbage collection.
You can read more about memory management in this article on TiDev which is still very relevant.
In your updated question you set all the sub-views in the data object. nulling the data object will also drop all references to the views, so that should be enough.

Flipswitch in lightswitch is going in an infinite loop

I got this piece of code for rendering and using Flipswitch as a custom control in lightswitch application.
function createBooleanSwitch(element, contentItem, trueText, falseText, optionalWidth) {
var $defaultWidth = '5.4em';
var $defaultFalseText = 'False';
var $defaultTrueText = 'False';
var $selectElement = $('<select data-role="slider"></select>').appendTo($(element));
if (falseText != null) {
$('<option value="false">' + falseText + '</option>').appendTo($selectElement);
}
else {
$('<option value="false">' + $defaultFalseText + '</option>').appendTo($selectElement);
}
if (trueText != null) {
$('<option value="true">' + trueText + '</option>').appendTo($selectElement);
}
else {
$('<option value="true">' + $defaultTrueText + '</option>').appendTo($selectElement);
}
// Now, after jQueryMobile has had a chance to process the
// new DOM addition, perform our own post-processing:
$(element).one('slideinit', function () {
var $flipSwitch = $('select', $(element));
// Set the initial value (using helper function below):
setFlipSwitchValue(contentItem.value);
// If the content item changes (perhaps due to another control being
// bound to the same content item, or if a change occurs programmatically),
// update the visual representation of the control:
contentItem.dataBind('value', setFlipSwitchValue);
// Conversely, whenver the user adjusts the flip-switch visually,
// update the underlying content item:
$flipSwitch.change(function () {
contentItem.value = ($flipSwitch.val() === 'true');
});
// To set the width of the slider to something different than the default,
// need to adjust the *generated* div that gets created right next to
// the original select element. DOM Explorer (F12 tools) is a big help here.
if (optionalWidth != null) {
$('.ui-slider-switch', $(element)).css('width', optionalWidth);
}
else {
$('.ui-slider-switch', $(element)).css('width', defaultWidth);
}
//===============================================================//
// Helper function to set the value of the flip-switch
// (used both during initialization, and for data-binding)
function setFlipSwitchValue(value) {
$flipSwitch.val((value) ? 'true' : 'false');
// Having updated the DOM value, refresh the visual representation as well
// (required for a slider control, as per jQueryMobile's documentation)
$flipSwitch.slider(); // Initializes the slider
$flipSwitch.slider('refresh');
// Because the flip switch has no concept of a "null" value
// (or anything other than true/false), ensure that the
// contentItem's value is in sync with the visual representation
contentItem.value = ($flipSwitch.val() === 'true');
}
});
}
This piece of code works fine. It renders the flipswitch on the screen. I am showing the data in an Edit screen, which is coming in a popup. Problem arises when I open that popup which contains the flipswitch and without changing any data on UI, I just try to close that popup screen. The IE hangs and it gives error saying that long script is running. When I debug the createBoolenaSwitch function, I came to know that it is going in infinite loop inside the function called setFlipSwitchValue(value){}
Why is this function getting called and this is going in an infinite loop?

Drag and Drop with Angular JS and JQuery

Couple of days ago I found this interesting post at http://www.smartjava.org/content/drag-and-drop-angularjs-using-jquery-ui and applied it into my website. However when I progressively using it there is a bug I identified, basically you can not move an item directly from one div to another's bottom, it has to go through the parts above and progress to the bottom. Anyone can suggest where does it goes wrong? The example is at http://www.smartjava.org/examples/dnd/double.html
Troubling me for days already.....
I did this a bit differently. Instead of attaching a jquery ui element inside the directive's controller, I instead did it inside the directive's link function. I came up with my solution, based on a blog post by Ben Farrell.
Note, that this is a Rails app, and I am using the acts_as_list gem to calculate positioning.
app.directive('sortable', function() {
return {
restrict: 'A',
link: function(scope, elt, attrs) {
// the card that will be moved
scope.movedCard = {};
return elt.sortable({
connectWith: ".deck",
revert: true,
items: '.card',
stop: function(evt, ui) {
return scope.$apply(function() {
// the deck the card is being moved to
// deck-id is an element attribute I defined
scope.movedCard.toDeck = parseInt(ui.item[0].parentElement.attributes['deck-id'].value);
// the id of the card being moved
// the card id is an attribute I definied
scope.movedCard.id = parseInt(ui.item[0].attributes['card-id'].value);
// edge case that handles a card being added to the end of the list
if (ui.item[0].nextElementSibling !== null) {
scope.movedCard.pos = parseInt(ui.item[0].nextElementSibling.attributes['card-pos'].value - 1);
} else {
// the card is being added to the very end of the list
scope.movedCard.pos = parseInt(ui.item[0].previousElementSibling.attributes['card-pos'].value + 1);
}
// broadcast to child scopes the movedCard event
return scope.$broadcast('movedCardEvent', scope.movedCard);
});
}
});
}
};
});
Important points
I utilize card attributes to store a card's id, deck, and position, in order to allow the jQuery sortable widget to grab onto.
After the stop event is called, I immediately execute a scope.$apply function to get back into, what Misko Hevery call,s the angular execution context.
I have a working example of this in action, up in a GitHub Repo of mine.

Resources