Can't stop execution in parasails component method till navigator.geolocation yields the result - sails.js

I need to take the visitor's location when clicking a ajax-button and only then send the form data to the action.
So far I am able to conditionally submit based on a class existence.
The problem is that the navigator.geolocation condition in my code doesn't get parsed despite the agree popup appearing to user and the OK being clicked.
It just skips this and display the fallback location.
Here is my changed default sails ajax-button component:
methods: {
click: async function () {
let btnDelayed = document.getElementById('btn-search');
if (btnDelayed && btnDelayed.classList.contains('delayed')) {
console.log('btn-search cannot emit click');
await this.loadGeoData();
} else {
console.log('btn-search can emit click ');
this.$emit('click');
}
},
loadGeoData: async function () {
let loc = await this.getLocation();
console.log('location');
console.dir(loc);
/*
let test = {
lat: 0.22,
lng: 0.55
};
*/
let btnSubmit = document.getElementById('btn-search');
btnSubmit.classList.remove('delayed');
let pos = JSON.stringify(loc);
this.$emit('pos', pos);
console.log('pos event value ' + pos);
this.click();
},
getLocation: async function () {
let loc = {
lat: 0,
lng: 0
};
if (navigator.geolocation) {
let options = {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
};
console.log('geolocation loaded');
navigator.geolocation.getCurrentPosition(function (position) {
console.log('getting position...');
console.dir(position);
loc.lat = position.coords.latitude;
loc.lng = position.coords.longitude;
}, (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
}, options);
}
return loc;
}
}
And here is the console log output:
btn-search cannot emit click
geolocation loaded
location
{…}
​
lat: 0
​
lng: 0
​
<prototype>: Object { … }
pos event value {"lat":0,"lng":0}
btn-search can emit click
// getting the data in the instance
argins
{…}
​
__ob__: Object { value: {…}, dep: {…}, vmCount: 0 }
​
actionref: "{\"lat\":0,\"lng\":0}"
actiontype: "word"
search: ​
The avigator.geolocation.getCurrentPosition gets though executed but adter the form is submit:
getting position... ajax-button.component.js:108:13
Position
​
coords: Coordinates { latitude: 44.00000000000, longitude: 26.00000000000, accuracy: 931, … }
​
timestamp: 1548163982134
_
Of course, I need it executed before.

For further readers,
After days of searching I found this post, Catch Geolocation Error - Async Await, explaining how to make geolocation work with async/await.
The rest was just a matter of modifying the ajax-button component click event.

Related

How can I increment a counter variable in LoopBack 4 with a MongoDB datasource?

I'm trying to convert my Nodejs Express app to Loopback 4 and I can't figure out how to increment a counter. In my Angular 9 app when a user clicks an icon a counter is incremented. This works perfectly in Express
In Express
const updateIconCount = async function (dataset, collection = 'icons') {
let query = { _id: new ObjectId(dataset.id), userId: dataset.userId };
return await mongoController.update(
collection,
query,
{ $inc: { counter: 1 } },
function (err, res) {
logAccess(res, 'debug', true, 'function update updateIconLink');
if (err) {
return false;
} else {
return true;
}
}
);
};
I tried to first get the value of counter and then increment but every time I save VS Code reformats the code in an an unusual way. In this snippet I commented out the line of code that causes this reformatting. I can set the counter value, e.g. 100.
In Loopback 4
#patch('/icons/count/{id}', {
responses: {
'204': {
description: 'Icons PATCH success',
},
},
})
async incrementCountById(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Icons, {partial: true}),
},
},
})
icons: Icons,
): Promise<void> {
// let targetIcon = this.findById(id).then(icon => {return icon});
icons.counter = 100;
console.log(icons.counter);
await this.iconsRepository.updateById(id, icons);
}
How do I implement { $inc: { counter: 1 } } in Loopback 4?
Added to aid solution
My mongo.datasource.ts
import {inject, lifeCycleObserver, LifeCycleObserver} from '#loopback/core';
import {juggler} from '#loopback/repository';
const config = {
name: 'mongo',
connector: 'mongodb',
url: '',
host: '192.168.253.53',
port: 32813,
user: '',
password: '',
database: 'firstgame',
useNewUrlParser: true,
allowExtendedOperators: true,
};
// Observe application's life cycle to disconnect the datasource when
// application is stopped. This allows the application to be shut down
// gracefully. The `stop()` method is inherited from `juggler.DataSource`.
// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html
#lifeCycleObserver('datasource')
export class MongoDataSource extends juggler.DataSource
implements LifeCycleObserver {
static dataSourceName = 'mongo';
static readonly defaultConfig = config;
constructor(
#inject('datasources.config.mongo', {optional: true})
dsConfig: object = config,
) {
super(dsConfig);
}
}
Amended endpoint
#patch('/icons/count/{id}', {
responses: {
'204': {
description: 'Icons PATCH success',
},
},
})
async incrementCountById(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Icons, {partial: true}),
},
},
})
icons: Icons,
): Promise<void> {
console.log(id);
// #ts-ignore
await this.iconsRepository.updateById(id, {$inc: {counter: 1}});//this line fails
// icons.counter = 101; //these lines will set the icon counter to 101 so I know it is connecting to the mongodb
// await this.iconsRepository.updateById(id, icons);
}
You can use the mongo update-operators.
Basically, you just have to set allowExtendedOperators=true at your MongoDB datasource definition (guide). After that, you can directly use these operators.
Usage example:
// increment icon.counter by 3
await this.iconsRepository.updateById(id, {$inc: {counter: 3}} as Partial<Counter>);
Currently, these operators are missing from the lb4 types so you must cheat typescript to accept them. It's ugly but that's the only solution I could find right now.
You can follow this issue to see what's going on with these operators.

Why does collection show as blank in console, but available in Meteor server?

I create a function that creates a whole bunch of tasks and it prints them all out out in the server function and displays on the server log. When I run Tasks.find().fetch() in the console, it returns a blank array. What am I doing wrong here? I have a few other objects that appear to be set up the same way and DO show in the console.
let createDummyTasks = function(){
var numberToFake=1000;
var equipment = ["Auto Folding Machine", "Binding Machine",
"Scissor Machine", "Folding Machine",
"Cutting Machine"];
for (let i = 0; i < numberToFake; i++) {
createDummyTask();
}
console.log(Tasks.find().fetch())
function createDummyTask(){
let name = faker.name.jobArea();
let status = randomStatus();
let duration = Math.floor(Math.random() * 40) + 1;
let startDate = faker.date.between('2017-09-10', '2017-09-17');
let endDate = faker.date.between('2017-09-18', '2017-09-30');
let equipment = Equipment.aggregate({$sample: {size: 1}});
// let thisEquipment = equipment[Math.floor(Math.random() * equipment.length)]
Tasks.insert({name: name,
status: status,
duration: duration,
startDate: startDate,
endDate: endDate
}, function(error){
if(error){
console.log("error");
} else {
console.log("success");
}
})
}
}
In 'collections' folder off of app root I have a task.js
Tasks = new Mongo.Collection('tasks');
Tasks.allow({
insert() {
// When we will ALLOW inserts on the client.
return false;
},
update() {
// When we will ALLOW updates on the client.
return false;
},
remove() {
// When we will ALLOW removes on the client.
return false;
}
});
Tasks.deny({
insert() {
// When we will DENY inserts on the client.
return true;
},
update() {
// When we will DENY updates on the client.
return true;
},
remove() {
// When we will DENY removes on the client.
return true;
}
});
and then I subscribe to the items on the client js
// *************************************************************
Template.schedule.onCreated( () => {
Template.instance().subscribe( 'customers' );
Template.instance().subscribe( 'jobs' );
Template.instance().subscribe( 'tasks' );
Template.instance().subscribe( 'equipment' );
});
Template.widget.onRendered(function(){
if(Meteor.isDevelopment){
Meteor.call('populateDummyInfo', (error)=>{
if(error){
console.log(error);
}
})
}
})
I didn't publish....
Meteor.publish('tasks', function () {
return Tasks.find();
});

hash format error! using routing

I have developed an OpenUI5 app ant it works fine!
But every time that I invoke the routing I have this message:
2015-07-15 16:15:45 hash format error! The current Hash: /line/01 -
log
error
onHashChange
detectHashChange
jQuery.event.dispatch
jQuery.event.add.elemData.handle
It is not a blocking problem but it is annoying because it dirty and fills thi debug console..!
To call the router I write:
this.router = sap.ui.core.UIComponent.getRouterFor(this);
this.router.navTo("activities", {
"id_line": '01'
});
and this is the routing file:
routes: [
...
{
pattern: "line/{id_line}",
name: "activities",
target: ["master_search", "detail_activities"]
},
...
],
targets: {
master_search: {
viewName: "UniversalMenu",
viewLevel: 1,
controlAggregation: "masterPages"
}
,
detail_activities: {
viewName: "DetailActivity",
viewLevel: 4
}
...
}
Edit: this is a snippet where I use jQuery.sap.history
jQuery.sap.require("jquery.sap.history");
jQuery.sap.require("sap.m.InstanceManager");
sap.ui.controller("ui5bp.view.App", {
getDefaultPage : function () {
return "Menu";
},
onInit : function () {
var historyDefaultHandler = function (navType) {
if (navType === jQuery.sap.history.NavType.Back) {
//this.navBack(this.getDefaultPage());
} else {
this.navTo(this.getDefaultPage(), null, false);
}
};
var historyPageHandler = function (params, navType) {
if (!params || !params.id) {
jQuery.sap.log.error("invalid parameter: " + params);
} else {
if (navType === jQuery.sap.history.NavType.Back) {
this.navBack(params.id);
} else {
this.navTo(params.id, params.data, false);
}
}
};
jQuery.sap.history({
routes: [{
// This handler is executed when you navigate back to the history state on the path "page"
path : "page",
handler : jQuery.proxy(historyPageHandler, this)
}],
// The default handler is executed when you navigate back to the history state with an empty hash
defaultHandler: jQuery.proxy(historyDefaultHandler, this)
});
// subscribe to event bus
var bus = sap.ui.getCore().getEventBus();
bus.subscribe("nav", "to", this.navHandler, this);
bus.subscribe("nav", "back", this.navHandler, this);
bus.subscribe("nav", "virtual", this.navHandler, this);
},
navHandler: function (channelId, eventId, data) {
if (eventId === "to") {
this.navTo(data.id, data.data, true);
} else if (eventId === "back") {
//**************************************************
// if(data && data.id){
// this.navBack(data.id);
// } else {
// jQuery.sap.history.back();
// }
var app = this.getView().app;
if(data.type==="master"){
app.backMaster();
}else if(data.type==="detail"){
app.backDetail();
}else{alert("back to master o detail?");};
//**************************************************
} else if (eventId === "virtual") {
jQuery.sap.history.addVirtualHistory();
} else {
jQuery.sap.log.error("'nav' event cannot be processed. There's no handler registered for event with id: " + eventId);
}
},
navTo : function (id, data, writeHistory) {
if (id === undefined) {
// invalid parameter
jQuery.sap.log.error("navTo failed due to missing id");
} else {
var app = this.getView().app;
// navigate in the app control
app.to(id, "slide", data);
}
},
/*
navBack : function (id) {
if (!id) {
// invalid parameter
jQuery.sap.log.error("navBack - parameters id must be given");
} else {
// close open popovers
if (sap.m.InstanceManager.hasOpenPopover()) {
sap.m.InstanceManager.closeAllPopovers();
}
// close open dialogs
if (sap.m.InstanceManager.hasOpenDialog()) {
sap.m.InstanceManager.closeAllDialogs();
jQuery.sap.log.info("navBack - closed dialog(s)");
}
// ... and navigate back
var app = this.getView().app;
var currentId = (app.getCurrentPage()) ? app.getCurrentPage().getId() : null;
if (currentId !== id) {
app.backToPage(id);
jQuery.sap.log.info("navBack - back to page: " + id);
}
}
}
*/
});
In Component.js I had 2 rows where I set up custom myNavBack and myNavToWithoutHash functions:
// 3a. monkey patch the router
var oRouter = this.getRouter();
oRouter.myNavBack = ui5bp.MyRouter.myNavBack; //to comment
oRouter.myNavToWithoutHash = ui5bp.MyRouter.myNavToWithoutHash; //to comment
I have started from an example of app skeleton for my app and then I have implemented the routing with the logic suggested from the framework.
This coexistence of two different methods to navigate produced the error in console. Tahnkyou #TimGerlach
After the comment of the two rows errors have vanished.

ProxyConnectionError when connecting as Anonymous

I have developed an operator to retrieve information from Orion Context Broker.
It works perfectly when I'm loggin but if I try to enter as anonymous (with the embedded URL) in a incognito window, the operator raises the next error:
(link to the image): http://i.stack.imgur.com/jxMkr.png
This is the code:
var doInitialSubscription = function doInitialSubscription() {
this.subscriptionId = null;
this.ngsi_server = MashupPlatform.prefs.get('ngsi_server');
this.ngsi_proxy = MashupPlatform.prefs.get('ngsi_proxy');
this.connection = new NGSI.Connection(this.ngsi_server, {
ngsi_proxy_url: this.ngsi_proxy
});
console.log("Send initial subscription");
var types = ['SMARTMETER'];
var entityIdList = [];
var entityId;
entityId = {
id: '.*',
type: 'SMARTMETER',
isPattern: true
};
entityIdList.push(entityId);
var attributeList = null;
var duration = 'PT3H';
var throttling = null;
var notifyConditions = [{
'type': 'ONCHANGE',
'condValues': condValues
}];
var options = {
flat: true,
onNotify: handlerReceiveEntity.bind(this),
onSuccess: function (data) {
console.log("Subscription success ID: "+data.subscriptionId);
this.subscriptionId = data.subscriptionId;
this.refresh_interval = setInterval(refreshNGSISubscription.bind(this), 1000 * 60 * 60 * 2); // each 2 hours
window.addEventListener("beforeunload", function () {
this.connection.cancelSubscription(this.subscriptionId);
}.bind(this));
}.bind(this),
onFailure: function(data) {
console.log(data);
}
};
console.log("Now creating subscription...");
this.connection.createSubscription(entityIdList, attributeList, duration, throttling, notifyConditions, options);
};
Any idea of what is wrong?
According to user comments on the question, updating to Orion 0.19.0 (following the DB upgrade procedure detailed here) solves the problem.

How to set location triggers dynamically in Worklight?

I need to create new geofence circles dynamically for each location I get from a Worklight Adapter. I need to do it using a loop, as I dunno how many locations are defined in backend service. Furthermore a new location can be define meanwhile the application is running.
I have seen several samples for an established set of locations but I dunno how to get it working in my scenario...
I have already found a solution. This is the code in case someone needs it:
var triggers = new Object();
triggers.Geo={};
triggers.Geo.Cliente = {
type: "Enter",
circle: {
longitude: xxxxxx,
latitude: xxxxxxx,
radius: proximidad // 300m
},
confidenceLevel: "high", // ~95% confidence that we are in the circle
eventToTransmit: {
event: {
name: 'clientecerca'
},
transmitImmediately: true
}
};
Generating Triggers using an external function:
var triggers = new Object();
triggers.Geo={};
triggers.Geo.Cliente = generaTriggers("41.43373","-3.80052");
function generaTriggers(lat,lon){
var Cliente ={
type: "Enter",
circle: {
longitude: lon,
latitude: lat,
radius: proximidad // 300m
},
confidenceLevel: "high", // ~95% confidence that we are in the circle
eventToTransmit: {
event: {
name: 'clientecerca'
},
transmitImmediately: true
}
};
return Cliente;
}
This is the final result for adding dynamically location triggers:
*Note: vector is a javascript array that contains the latitude and longitude of the different geofences circles*
function Geofencing(){
var policy = { Geo: WL.Device.Geo.Profiles.LiveTracking() };
var triggers = new Object();
triggers.Geo={};
var triggersgenerados = generaTriggers();
triggers.Geo = triggersgenerados;
WL.Device.startAcquisition(policy, triggers, geoFailure);
WL.App.setKeepAliveInBackground(true);
}
function generaTriggers(){
var triggersvisitas= new Object;
for(var i=0; i< vector.length;i++){
var Cliente ={
type: "Enter",
circle: {
longitude: vector[i].longitud,
latitude: vector[i].latitud,
radius: proximidad // 300m
},
confidenceLevel: "high",
eventToTransmit: {
event: {
name: 'clientecerca'
},
transmitImmediately: true
}
};
triggersvisitas["Cliente"+i]=Cliente;
}
return triggersvisitas;
}