How can I delete a feature after a callback on openlayers - maps

I try to create a code who can create a feature and delete it if it dosen't respect some rules. (in this exemple I just delete it just after his creation)
Below is my code, I create a map and I allow to create a polygon on. Next I try to delete it when an event drawend is triggered.
init(){
this.message = "";
var raster = new TileLayer({
source: new XYZ({
url: 'http://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
attributions: [
'© Google','Terms of Use.'
]
})
});
this.source = new VectorSource();
this.vector = new VectorLayer({
source: this.source
});
this.map = new Map({
layers: [raster, this.vector],
target: 'map',
view : new View({
center: [0,0],
zoom : 4
})
});
this.addInteraction();
}
addInteraction() {
var self = this;
this.draw = new Draw({
source: this.source,
type: "Polygon"
});
this.draw.on('drawend', function (e) {
self.message = "";
self.map.removeInteraction(self.draw);
self.selectedFeature = e.feature;
self.removeSelectedFeature();
// This works but I don't like this solution
//setTimeout(function(){self.removeSelectedFeature()}, 1);
self.geoJSON = self.getCoord();
});
this.map.addInteraction(this.draw);
}
removeSelectedFeature()
{
this.source.removeFeature(this.selectedFeature);
this.selectedFeature = null;
this.validated = false;
this.addInteraction();
}
but When I try to execute this code, I got this error :
core.js:14597 ERROR TypeError: Cannot read property 'forEach' of undefined
at VectorSource.removeFeatureInternal (Vector.js:951)
at VectorSource.removeFeature (Vector.js:939)
at MapComponent.push../src/app/map/map.component.ts.MapComponent.removeSelectedFeature (map.component.ts:106)
at Draw.<anonymous> (map.component.ts:97)
at Draw.boundListener (events.js:41)
at Draw.dispatchEvent (Target.js:99)
at Draw.finishDrawing (Draw.js:872)
at Draw.handleUpEvent (Draw.js:578)
at Draw.handleEvent (Pointer.js:132)
at Draw.handleEvent (Draw.js:524)
I know it's because my feature isn't exist yet, and it will created after the event will finish. hence my question, how can I delete a feature after it was created ?
I tried using a event button but it's not a good idea I would like that the user don't need to click on a button for execute checks.
I tried using a setTimeout (as we can see in my commented code) but I don't like this solution (even if it works)
Anyone can explain me how can I do the job ?
Thanks in advance ;)

have you tried something like this in your drawend, if your check failed:
source.once('addfeature', (event) => {
const feature = event.feature;
source.removeFeature(feature);
});
this listens to the 'addfeature' event once and then removes the listener.

Related

Jaydata saveChanges() counts tracked / changed entities, but doesn't send a batch request (with OData v4 Provider and Web Api)

by working with jaydata i am adding entities to a tree structure with deep nesting of entity-objects.
I attach the upper entity to the context and edit/add/remove related child entities. At the end i use saveChanges() with a promise.
The count-value passed to the promise tells that all changed entities have been counted correctly but saveChanges() didn't execute a batch request, persisting these entities.
So it feels like nothing else happens, but counting entities.
I post a small code example. I am quite sure, that the references of the entites are set correctly. (Working with jaydata, odata v4, web api and angularjs)
Is someone else having this problem with jaydata and found the reason?
Thanks for your help. =)
Greetings Paul
// Attach upper entity
DataService.jaydata.attach(viewModel.currentSkillTree.entity);
// Generating new entities
var newSkill = new DataService.jaydata.Skills.elementType({
Id: undefined,
Name: 'New skill',
Levels: [],
IconId: 47,
SkillTreeUsage: []
});
var newSkillLevel = new DataService.jaydata.SkillLevels.elementType({
Id: undefined,
ShortTitle: 'New level',
Skill: newSkill,
SkillId: undefined,
Level: 1,
RequirementSets: []
});
var newRequirementSet = new DataService.jaydata.RequirementSets.elementType({
Id: undefined,
SkillLevel: newSkillLevel,
SkillLevelId: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
});
var newSkillTreeElement = new DataService.jaydata.SkillTreeElements.elementType({
Id: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
Skill: newSkill,
SkillId: undefined,
Position: { X: x, Y: y }
});
// Completing object-references
viewModel.currentSkillTree.entity.Elements.push(newSkillTreeElement);
newSkill.Levels.push(newSkillLevel);
newSkill.SkillTreeUsage.push(newSkillTreeElement)
newSkillLevel.RequirementSets.push(newRequirementSet);
// Saving
DataService.jaydata.saveChanges()
.then(function (cnt) {
console.log('Saved entities:', cnt);
// The cnt-result in console is 4
// But no request was executed, nothing was saved
}, function (exception) {
console.log(exception); // Also no exception was thrown
});
So to not be that unkind.
The solution to solve the problem above to me, since i tried nearly every combination with entities (adding, attaching, .save(), .saveChanges(), object-references etc, figuring out it doesn't make sense anyway, it just acted the same way and seems to be so buggy), ended up within a workaround acting with classic nested async calls.
The solution was to save entities seperately within nested promises and to turn off the batch behavior of jaydata, to avoid double requests.
You can find the option within $data.defaults
$data.defaults.OData.disableBatch = true;
As result i am dealing now with good old nasty pyramids of doom, which at least gave the possibility back to save entities in the right order, with full control, the way the api needs it.
// Saving new SkillLevelRequirement connection
if (isConnectionGiven === false) {
// The first level of source skill where the target-skill-requirement will be added
var sourceSkillLevel = Enumerable
.From(sourceSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === 1;
});
// The last level of the target-skill to solve
var targetSkillLevel = Enumerable
.From(targetSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === targetSkill.Levels.length;
});
// First set of first level from source skill (will be used to add skilllevel-requirement)
var firstRequirementSet = sourceSkillLevel.RequirementSets[0];
// New RequirementAsignment
var newRequirementAssignment = new DataService.jaydata.RequirementAssignments.elementType({
RequirementSetId: firstRequirementSet.Id,
Order: 1
});
// New Requirement
var newRequirement = new DataService.jaydata.Requirements.elementType({
Title: requirementTypes.SKILL_CONNECTION,
RequirementOfIntId: undefined,
RequirementOfBoolId: undefined,
RequirementOfSkillLevelId: 0
});
// New RequirementOfSkillLevel
var newRequirementOfSkillLevel = new DataService.jaydata.RequirementsOfSkillLevel.elementType({
SkillLevelId: targetSkillLevel.Id,
});
// Loading symbol
showBusyIndicator();
newRequirementOfSkillLevel.save()
.then(function () {
newRequirement.RequirementOfSkillLevelId = newRequirementOfSkillLevel.Id;
newRequirement.save()
.then(function () {
newRequirementAssignment.RequirementId = newRequirement.Id;
newRequirementAssignment.save()
.then(function () {
// Loading symbol will be closed after tree reloaded
reloadCurrentTree();
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}
}
#jaydata developers: Thanks for 42 new grey hairs. I'm still at the point where i think i am using your tool wrong and jaydata could do so much better. Better up your documentation, sieriously. No desserts for you today.

passing data from service to a controller in angular

I am having a small issue sending data from service to a controller with google API. can anyone have a look at code below and give me some advice?
injection is good and I don't see any errors. I tried few things.
1. normal binding using a service method(e.g. getCurrentPos()). it will return an object that stores the pos info
2. $rootScope.$broadcast
3. angular.copy()
//in the service--------
var position = new google.maps.LatLng(markers[i][1], markers[i][2]);
this.marker = new google.maps.Marker({
position: position,
map: map,
title: markers[i][0],
draggable: true,
icon: '../img/png/shopper1.png'
});
var personMarker = this.marker;
this.marker.addListener('drag', function() {
console.log('lat:'+personMarker.getPosition().lat()+' lng:'+personMarker.getPosition().lng());
currentPos.lat = personMarker.getPosition().lat();
currentPos.lng = personMarker.getPosition().lng();
// myPos = personMarker.getPosition();
myPos = [personMarker.getPosition().lat(), personMarker.getPosition().lng()];
angular.copy(myPos, scope.currentPos); //not working
console.log('scope.currentPos',scope.currentPos);
// $rootScope.$broadcast('evtUpdateMyPos', { //tried but not working.
// 'lat': personMarker.getPosition().lat(),
// 'lng': personMarker.getPosition().lng()
// });
console.log("mypos:", myPos);
});
//in the controller-------
$scope.currentPos = [];
//Listen on a broadcast event
// $scope.$on('evtUpdateMyPos', function (event, myPos){
// console.log('evtUpdateMyPos is fired.', myPos); //this logs here.
// // $scope.currentPos = angular.copy(myPos); //this dos not help
// // $scope.currentPos = myPos; //this does not hlep.
// })
In some cases when you're trying to update your model from an external library like in your case using google maps you should wrap the setter in something like this:
$timeout(function(){
myModel = 'Google.map.data'
});
or
$rootScope.$applyAsync(function(){ // this could also be $scope
myModel = 'Google.map.data'
});
myModel could be a property of your service or a $scope variable ($scope.myModel)

I'm not calling $apply explicitly but still get Error: [$rootScope:inprog] $apply already in progress

In a angular factory I have a method to create a new item, which has a connection to a user and a price to add to that users "items" array (like a shopping cart). So I have to see if the user is present in my the local users array if not then on the server and if not then create the user.
Code looks like this:
var saveItem = function (item) {
var user = filterUserById(item.ownerId);
if (user) {
user.createItem(item);
} else {
repository.getUserById(item.ownerId).then(
function (serverUser) {
var userViewModel = repository.getUserViewModel(serverUser);
userViewModel.createItem(item);
users.push(userViewModel);
}
, function () {
user = {
id: item.ownerId,
items: [
createItemDto(item)
]
};
repository.createUser({ id: user.id }, user);
users.push(repository.getUserViewModel(user));
});
}
};
No matter which of the "cases" occurs (user was found localy, on the server or was created and added) I get an error:
Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.3.0-beta.18/$rootScope/inprog?p0=%24apply
I recon this may have to do with the fact that I'm using resources in my repository, but I don't think resource should (since it's a part of angular..). Here's the user.createItem method, code:
user.createItem = function (item) {
var resource = userResource
, itemDto = createItemDto(item)
, command = [{
Type: 'add',
Name: 'items',
Value: itemDto
}];
resource.createItem({ id: item.ownerId }, command);
this.items.push(itemDto);
};
Y U NO WERK!? PLS HLP! :'(
P.S. I don't have any explicit calls to apply, compile or digest anywhere in my code.
Found the problem! I had put a small code line to set focus on the correct input after the item was added and form was emptied. This consisted of a
$('selector').focus();
This was colliding with digest cycle... Solution:
$timeout($('selector').focus());
Try wrapping your call to user.createItem(item) in a $timeout function:
$timeout(function() {
user.createItem(item);
}, 0);
It's possible you could be triggering some other call to $scope.$apply() some other way.
Alternatively, try using $scope.$evalAsync(function())
Here's some good info: inprog

Backbone.Model: set collection as property

I'm new with backbone and faced the following problems. I'm trying to emulate some sort of "has many relation". To achieve this I'm adding following code to initialize method in the model:
defaults: {
name: '',
tags: []
},
initialize: function() {
var tags = new TagsCollection(this.get('tags'));
tags.url = this.url() + "/tags";
return this.set('tags', tags, {
silent: true
});
}
This code works great if I fetch models through collection. As I understand, first collection gets the data and after that this collection populates models with this data. But when I try to load single model I get my property being overridden with plain Javascript array.
m = new ExampleModel({id: 15})
m.fetch() // property tags get overridden after load
and response:
{
name: 'test',
tags: [
{name: 'tag1'},
{name: 'tag2'}
]
}
Anyone know how to fix this?
One more question. Is there a way to check if model is loaded or not. Yes, I know that we can add callback to the fetch method, but what about something like this model.isLoaded or model.isPending?
Thanks!
"when I try to load single model I get my property being overridden with plain Javascript array"
You can override the Model#parse method to keep your collection getting overwritten:
parse: function(attrs) {
//reset the collection property with the new
//tags you received from the server
var collection = this.get('tags');
collection.reset(attrs.tags);
//replace the raw array with the collection
attrs.tags = collection;
return attrs;
}
"Is there a way to check if model is loaded or not?"
You could compare the model to its defaults. If the model is at its default state (save for its id), it's not loaded. If it doesn't, it's loaded:
isLoaded: function() {
var defaults = _.result(this, 'defaults');
var current = _.wíthout(this.toJSON(), 'id');
//you need to convert the tags to an array so its is comparable
//with the default array. This could also be done by overriding
//Model#toJSON
current.tags = current.tags.toJSON();
return _.isEqual(current, defaults);
}
Alternatively you can hook into the request, sync and error events to keep track of the model syncing state:
initialize: function() {
var self = this;
//pending when a request is started
this.on('request', function() {
self.isPending = true;
self.isLoaded = false;
});
//loaded when a request finishes
this.on('sync', function() {
self.isPending = false;
self.isLoaded = true;
});
//neither pending nor loaded when a request errors
this.on('error', function() {
self.isPending = false;
self.isLoaded = false;
});
}

Accessing NotMapped Computed Properties Using BreezeJS and AngularJS

I've followed the examples laid out in http://www.breezejs.com/documentation/extending-entities, and the DocCode test labeled "unmapped property can be set by server class calculated property".
When I try and access the unmapped property, it has the same value as it did when it was set in the constructor. I have verified that the server is returning the unmapped property. It is possible that I don't know the proper way to access the unmapped property when using angularjs with breezejs.
See below on the two lines I've commented as "empty string, value expected".
breeze.NamingConvention.camelCase.setAsDefault();
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
var ms = new breeze.MetadataStore();
var manager = new breeze.EntityManager({
serviceName: 'breeze/Projects',
metadataStore: ms
});
manager.enableSaveQueuing(true);
manager.fetchMetadata()
.then(function () {
var Sheet = function ()
{
this.previewUrl = "";
}
ms.registerEntityTypeCtor("Sheet", Sheet);
var query = new breeze.EntityQuery("Project")
.withParameters({ id: $stateParams.projectId });
manager
.executeQuery(query)
.then(function (data)
{
var p = data.results[0]; // project
var s = p.sheets[0];
console.log(s["previewUrl"]); // empty string, value expected
console.log(s.previewUrl); // empty string, value expected
})
.fail(function (e)
{
console.log(e.message);
alert(e);
});
});
Edit: Additional Info
I've figured out that this is caused by the camelCase naming convention. I was able to fix the bug in breeze.js by altering the getPropertyFromServerRaw function to this:
function getPropertyFromServerRaw(rawEntity, dp) {
return rawEntity[dp.nameOnServer || dp.isUnmapped && rawEntity.entityType.metadataStore.namingConvention.clientPropertyNameToServer(dp.name)];
}
Hopefully this fix can make it into the next version of breeze.js

Resources