AngularJS - orderBy distance function - javascript

I'm extremely new to AngularJS so go easy on me... :-) I am in the process of building a new PhoneGap app with Ionic Framework and AngularJS. I have a list of locations that outputs in a list view and a function that will look up the user's location and get the distance between their current location and the location in the list. These currently both work correctly and I can see my list, sort by normal fields (name, etc).
What I would like to do is to have a preferences screen where the user will be able to set their preferred sorting option. I have already setup my basic preference controller as well that currently only is storing the preference to sort by 'name' but I'd like it to sort by distance that is calculated by a function seen below. But since the distance function seems to be in this controller, I don't know how to make it sort by that? Do I need to make a filter that runs the distance function?
Again, I'm new so any help would be greatly appreciated!
Here is my controller:
.controller('LocationsCtrl', function($scope, $ionicLoading, $ionicPopup, LocationsService, SettingsService) {
$scope.locations = {};
$scope.navTitle = "List of Locations";
$scope.rightButtons = [{
type: 'button-icon button-clear ion-more',
tap: function(e) {
$scope.openSortModal();
}
}];
// Method called on infinite scroll
// Receives a "done" callback to inform the infinite scroll that we are done
$scope.loadMore = function() {
$timeout(function() {
// Placeholder for later
$scope.$broadcast('scroll.infiniteScrollComplete');
}, 1000);
}
$scope.loading = $ionicLoading.show({
content: 'Getting current location...',
showBackdrop: false
});
navigator.geolocation.getCurrentPosition(function(pos) {
var coords = $scope.currentLocation = [pos.coords.longitude, pos.coords.latitude];
$scope.locations = LocationsService.allSync();
$scope.sortLoc = SettingsService.get('sortLocBy');
$ionicLoading.hide();
}, function(error) {
$ionicPopup.alert({
title: 'Unable to get location: ' + error.message
}).then(function(res) {
$ionicLoading.hide();
// not working
});
});
$scope.distanceFromHere = function (_item, _startPoint) {
var start = null;
var radiansTo = function (start, end) {
var d2r = Math.PI / 180.0;
var lat1rad = start.latitude * d2r;
var long1rad = start.longitude * d2r;
var lat2rad = end.latitude * d2r;
var long2rad = end.longitude * d2r;
var deltaLat = lat1rad - lat2rad;
var deltaLong = long1rad - long2rad;
var sinDeltaLatDiv2 = Math.sin(deltaLat / 2);
var sinDeltaLongDiv2 = Math.sin(deltaLong / 2);
// Square of half the straight line chord distance between both points.
var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) +
(Math.cos(lat1rad) * Math.cos(lat2rad) *
sinDeltaLongDiv2 * sinDeltaLongDiv2));
a = Math.min(1.0, a);
return 2 * Math.asin(Math.sqrt(a));
};
if ($scope.currentLocation) {
start = {
longitude: $scope.currentLocation[0],
latitude: $scope.currentLocation[1]
};
}
start = _startPoint || start;
var end = {
longitude: _item.location.lng,
latitude: _item.location.lat
};
var num = radiansTo(start, end) * 3958.8;
return Math.round(num * 100) / 100;
}
})
And here is my template:
<ion-view title="{{navTitle}}" left-buttons="leftButtons">
<ion-content header-shrink scroll-event-interval="5">
<ion-list class="locations-list">
<ion-item class="location-item" ng-repeat="loc in locations | orderBy:sortLoc" type="item-text-wrap" href="#/location/{{loc.id}}/details" style="background-image:url('{{loc.photos.0.url}}');">
<div class="location-title">
<p>{{loc.name}}</p>
<span class="distance-indicator"><span class="digit">{{distanceFromHere(loc)}}</span><span class="unit">mi</span></span>
</div>
</ion-item>
</ion-list>
</ion-content>
</ion-view>

See the orderBy docs. As the orderBy expression, you can use a function which generates a value to use for sorting. All you should need to do is to put the distanceFromHere function in your sortLoc scope variable (or change the filter to orderBy:distanceFromHere).

Related

Javascript sanity check of netsuite suitescript

Currently, I have a Netsuite SuiteScript where I export saved searches to csv's. It runs but it's not reusable and I'd like to make it easier by just adding key-value pairs. I have to a lot of copy pasting and it's easy to forget to update to the latest iteration of the run. It's a scheduled search which means it runs every 15 minutes and makes it hard to debug and test.
Right now, my code looks like this, but with more searches, and it's a pain is not reusable.
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'],
/**
* #param {record} record
* #param {search} search
*/
function(task) {
var FILE_ID = 2992639;
var SEARCH_ID = 2993;
function execute(scriptContext) {
//first search
var searchTask1 = task.create({
taskType: task.TaskType.SEARCH
});
searchTask1.savedSearchId = SEARCH_ID;
searchTask1.fileId = FILE_ID;
var searchTaskId1 = searchTask1.submit();
//next search
FILE_ID = 2992640;
SEARCH_ID = 3326;
var searchTask2 = task.create({
taskType: task.TaskType.SEARCH
});
searchTask2.savedSearchId = SEARCH_ID;
searchTask2.fileId = FILE_ID;
var searchTaskId2 = searchTask2.submit();
//next search
FILE_ID = 2992634;
SEARCH_ID = 3327;
var searchTask3 = task.create({
taskType: task.TaskType.SEARCH
});
searchTask3.savedSearchId = SEARCH_ID;
searchTask3.fileId = FILE_ID;
var searchTaskId3 = searchTask3.submit();
//this pattern repeats 19 times total.
}
return {
execute: execute
};
});
I've tried to code below
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'],
/**
* #param {record} record
* #param {search} search
*/
function(task) {
const searchList = {
2993:2992639,
3326:2992640,
3327:2992634
};
function execute(scriptContext) {
for (const [key, value] of Object.entries(searchList)) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = $key;
searchTask.fileId = $value;
var searchTaskId = searchTask.submit();
}
}
return {
execute: execute
};
});
but get the following error, and I'm not sure what is wrong with my syntax. Netsuite makes it hard to tell what I'm doing wrong, so I'm hoping someone can help here. Thanks!
{"type":"error.SuiteScriptError","name":"SSS_MISSING_REQD_ARGUMENT","message":"task.submit: Missing a required argument: SearchTask.savedSearchId","stack":["createError(N/error)","execute(/SuiteScripts/dashboardreports.js:224)","createError(N/error)"],"cause":{"name":"SSS_MISSING_REQD_ARGUMENT","message":"task.submit: Missing a required argument: SearchTask.savedSearchId"},"id":"","notifyOff":false,"userFacing":true}
I would use a custom record to store the search and file IDs, so you can add/update without modifying code. Then I would do something like the code below. This code first queries your custom record to get all of the search and file ids, then for each one, starts a new task.
/**
* #NApiVersion 2.0
* #NScriptType ScheduledScript
* #NModuleScope Public
*/
define(['N/log', 'N/search', 'N/task'], function(log, search, task) {
function execute(context) {
var searchInfos = getSearchInfo();
searchInfos.forEach(function(searchInfo) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = searchInfo.searchId;
searchTask.fileId = searchInfo.fileId;
var searchTaskId = searchTask.submit();
log.debug({ title: 'searchTaskId', details: searchTaskId });
});
}
function getSearchInfo() {
var results = search.create({
type: 'customrecord_search_to_csv_info',
filters: [
['isinactive', 'is', 'F']
],
columns: [
'custcolumn_search_id',
'custcolumn_file_id'
]
}).run().getRange({ start: 0, end: 1000 });
return (results || []).map(function(result) {
return {
searchId: result.getValue({ name: 'custcolumn_search_id '}),
fileId: result.getValue({ name: 'custcolumn_file_id' })
}
});
}
return {
execute: execute
};
});

Display certain posts based on User Location

I'm trying to figure out how to display certain mongoDB posts depending on the user's location. I have set up a search functionality that lets people search for MongoDB posts. I also have figured out how to get a user's location and find a big city near them.
Let's say you're in Washington DC. I want only posts containing Washington DC in the title to show up on a "show" page. I've been unable to figure this out.
Any advice?
Thanks!
Node & MongoDB set up to handle search request:
router.get("/", function(req, res){
if (req.query.search) {
const regex = new RegExp(req.query.search, 'i');
Deals.find({ "name": regex }, function(err, founddeals) {
if(err){
console.log(err);
} else {
res.render("deals/index",{deals:founddeals});
}
});
}
Set up to get the user's location.
This also returns the city nearest to the user
// Get User's Coordinate from their Browser
window.onload = function() {
// HTML5/W3C Geolocation
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(UserLocation);
}
// Default to Washington, DC
else
NearestCity(38.8951, -77.0367);
}
// Callback function for asynchronous call to HTML5 geolocation
function UserLocation(position) {
NearestCity(position.coords.latitude, position.coords.longitude);
}
// Convert Degress to Radians
function Deg2Rad(deg) {
return deg * Math.PI / 180;
}
function PythagorasEquirectangular(lat1, lon1, lat2, lon2) {
lat1 = Deg2Rad(lat1);
lat2 = Deg2Rad(lat2);
lon1 = Deg2Rad(lon1);
lon2 = Deg2Rad(lon2);
var R = 6371; // km
var x = (lon2 - lon1) * Math.cos((lat1 + lat2) / 2);
var y = (lat2 - lat1);
var d = Math.sqrt(x * x + y * y) * R;
return d;
}
var lat = 20; // user's latitude
var lon = 40; // user's longitude
var cities = [
["ATL", 33.740231, -84.394521],
["NYC", 40.748163, -73.985946],
["Vegas", 34.825425, -82.545665]
];
function NearestCity(latitude, longitude) {
var mindif = 99999;
var closest;
for (index = 0; index < cities.length; ++index) {
var dif = PythagorasEquirectangular(latitude, longitude, cities[index][1], cities[index][2]);
if (dif < mindif) {
closest = index;
mindif = dif;
}
}
// echo the nearest city
alert(cities[closest]);
console.log(closest)
}
I Hope you are using mongoose. Please try this code in your server. I have used $regex and made a small modification for your code.
router.get("/", function(req, res){
if (req.query.search) {
Deals.find({ "name":{ $regex: req.query.search } }, function(err, founddeals) {
if(err){
console.log(err);
} else {
res.render("deals/index",{deals:founddeals});
}
});
}

How to properly structure class in Node.js

I have a class called TileStreamer that I am currently defining as follows:
function TileStreamer {
};
This class has constants, which I define as follows:
// Tiles are 256 x 256 pixels
TileStreamer.prototype.TILE_SIZE = 256;
// Header size in bytes
TileStreamer.prototype.HEADER_SIZE = 28;
// Various table entry sizes in bytes
TileStreamer.prototype.RESOLUTION_ENTRY_SIZE = 12;
TileStreamer.prototype.TILE_COUNT_SIZE = 4;
TileStreamer.prototype.TILE_ENTRY_SIZE = 12;
// Offsets within header
TileStreamer.prototype.WIDTH_OFFSET = 3;
TileStreamer.prototype.HEIGHT_OFFSET = 4;
TileStreamer.prototype.NUM_TABLES_OFFSET = 7;
TileStreamer.prototype.UNPOPULATED_OFFSET = 12092;
There also other variables. These variables are important because they need to be accessible from other classes. They get their values within the methods of this class. This is what I am unsure of as far as structure. What I'm currently trying is:
TileStreamer.prototype.header;
TileStreamer.prototype.resolutionEntry;
TileStreamer.prototype.resolutionTable;
TileStreamer.prototype.filepath;
TileStreamer.prototype.s3;
TileStreamer.prototype.level;
TileStreamer.prototype.ncols;
TileStreamer.prototype.nrows;
TileStreamer.prototype.nlevels;
TileStreamer.prototype.toffset;
TileStreamer.prototype.tsize;
TileStreamer.prototype.modifiedTime;
TileStreamer.prototype.tile;
TileStreamer.prototype.host;
TileStreamer.prototype.bucket;
This class also has methods such as:
TileStreamer.prototype.Init = function(filepath, index, s3config){
var retval = false;
AWS.config.update({accessKeyId: s3config.access_key, secretAccessKey: s3config.secret_key});
var blc = new BlockLibraryConfigs();
var awsConfig = blc.awsConfig;
AWS.config.update({region: awsConfig.region});
var aws = new AWS.S3();
var params = {
Bucket: s3config.bucket,
Key: s3config.tile_directory + filepath,
Range: 'bytes=0-' + (this.HEADER_SIZE - 1)
};
aws.getObject(params, function(err, data){
if(err == null){
TileStreamer.modifiedTime = data.LastModified;
var header = bufferpack.unpack('<7I', data.Body);
TileStreamer.header = header;
TileStreamer.nlevels = header[TileStreamer.NUM_TABLES_OFFSET];
if(TileStreamer.nlevels == 5){
TileStreamer.level = 0;
TileStreamer.ncols = Math.ceil((header[TileStreamer.WIDTH_OFFSET] * 1.0) / TileStreamer.TILE_SIZE);
TileStreamer.nrows = Math.ceil((header[TileStreamer.HEIGHT_OFFSET] * 1.0) / TileStreamer.TILE_SIZE);
}
}
});
};
The method above should set some of the values of the variables, such as modifiedTime so that I can access it in another class such as:
TileStreamer = require('tilestreamer.js');
var ts = new TileStreamer();
ts.Init(parPath, index, config);
var last_modified = ts.modifiedTime;
Just put any public properties you want to initialise when the object is created, directly in the init function. Here's a small example...
function TileStreamer() {
};
TileStreamer.prototype.Init = function() {
this.modifiedTime = new Date();
};
var ts = new TileStreamer();
ts.Init();
console.log(ts);
jsfiddle example
https://jsfiddle.net/v6muohyk/
To get around the issue you're having with setting the object properties in a callback from an asynchronous function, just create a locally accessible variable to reference the object that you are creating at that time...
TileStreamer.prototype.Init = function() {
var thisTileStreamer = this;
asynchFunction(function(err, data) {
thisTileStreamer.modifiedTime = data.lastModified;
});
};
To take it one step further, if you need to execute some code after the init function has completed, then that will require waiting for the asynchronous function to complete, as well. For that, pass a further parameter to init, that is a function to be executed after all the work is done...
TileStreamer.prototype.Init = function(callback) {
var thisTileStreamer = this;
asynchFunction(function(err, data) {
thisTileStreamer.modifiedTime = data.lastModified;
callback();
});
};
var ts = new TileStreamer();
ts.Init(function() {
// put code here that needs to be executed *after* the init function has completed
alert(ts.modifiedTime);
});

$interval is not allowing me to update my front end

I'm trying to create a landing page where the picture and accompanying caption updates, showing all of the images and captions in an array of objects (pictures with captions). I think it needs to use $interval or $timeout.
My JS that will show a single picture that does not update:
.controller('userCtrl', function ($scope, Auth, $location, $timeout) {
var Picture = Parse.Object.extend("Picture");
var query = new Parse.Query(Picture);
query.find({
success: function(pictures){
var x = pictures.length;
var getRandomImage = function () {
var imageCount = x
var index = Math.floor(
( Math.random() * imageCount * 2 ) % imageCount);
return( pictures[ index ] );
}
$scope.image = getRandomImage();
})
My JS that I think should work, creating a randomly scrolling line of pictures:
.controller('userCtrl', function ($scope, Auth, $location, $timeout) {
var Picture = Parse.Object.extend("Picture");
var query = new Parse.Query(Picture);
query.find({
success: function(pictures){
var x = pictures.length;
var getRandomImage = function () {
var imageCount = x
var index = Math.floor(
( Math.random() * imageCount * 2 ) % imageCount);
return( pictures[ index ] );
}
$scope.image = $timeout(function(){getRandomImage();}, 3000);
})
The key portion of my HTML:
<img id="logo" src= {{image.get('logo').url()}}>
<p>
This is a picture ${{image.get('caption')}}
</p>
You can move assigning inside interval func, and also you don't need create function getRandomImage on every callback, so you can try something like this
.controller('userCtrl', function ($scope, Auth, $location, $timeout) {
function getRandomImage(pictures) {
var imageCount = pictures.length,
index = Math.floor( ( Math.random() * imageCount * 2 ) % imageCount);
return( pictures[ index ] );
}
var Picture = Parse.Object.extend("Picture");
var query = new Parse.Query(Picture);
query.find({
success: function(pictures){
$interval(function(){$scope.image = getRandomImage(pictures);}, 3000);
}
});
}
This line:
$scope.image = $timeout(function(){getRandomImage();}, 3000);
is placing the return value of the $timeout call into the $scope.image variable, and that return value (as per the $timeout documentation) is a promise object.
Instead, you probably want to place the return value of the getRandomImage function into your image variable, after a certain timeout, like this:
$timeout(function(){$scope.image = getRandomImage();}, 3000);

Force Meteor To Refresh / Re-render Templates?

*For reference I'm using iron router.
Instead of a sign in page I have this global sign in form embedded in an nav (aka on every page).
Right now I'm doing a really hacky refresh to reload the page once a user logs in.
I would like to just reload to the template aka not refresh the whole page.
Basically just want the templates rendered function to rerun on login.
Here's my current login code:
'submit #login': function(event, template){
event.preventDefault();
var handle = template.find('#usernameLogin').value;
var secretKey = template.find('#passwordLogin').value;
Meteor.loginWithPassword(handle, secretKey, function(err){
if (err) {
alert(err);
}else{
$('#close').click();
/* replace this with reactive ajax or whatever when you can! */
Meteor._reload.reload();
}
});
},
My render function which I think may be the real issue now:
Template.tournament.rendered = function () {
thisCampaign = this.data;
var self = this;
if (this.data.tournament.live) {
/* if theres a registered user */
if (Meteor.userId()) {
/* Select a winner box */
var participants = $('.participant-id');
var currentParticipant;
var nextRound;
var thisMatch;
var nextMatch;
var bracket;
participants.map(function(index, value){
if ($(value).text() === Meteor.userId()) {
if ($(value).parent().find('.participant-status').text() === 'undetermined') {
nextRound = $(value).parent().find('.participant-round').text();
thisMatch = $(value).parent().find('.participant-match').text();
bracket = $(value).parent().parent().parent().find('.participant');
};
};
});
nextRound = parseInt(nextRound) + 1;
nextMatch = Math.round(parseInt(thisMatch)/2) - 1;
if (parseInt(thisMatch) % 2 != 0) {
currentParticipant = 0;
}else{
currentParticipant = 1;
}
var winnerOptions = '';
var winnerBox = $('<div class="select-winner">');
if (bracket) {
bracket.map(function(index, value) {
winnerOptions += '<span class="winner-option"> '+$(value).find('.participant-title').text()+' <div class="winner-info"> '+$(value).find('a').html()+' </div> </span>'
});
winnerBox.append(winnerOptions);
$($($('.round'+nextRound).find('li')[nextMatch]).find('.participant')[currentParticipant]).removeClass('loser').addClass('undetermined');
$($($('.round'+nextRound).find('li')[nextMatch]).find('.participant')[currentParticipant]).find('a').addClass('tooltip').html(winnerBox);
};
}else{
}
}else{
/* Tournament Start Time */
var tournamentStartTime = function(){
var d = new Date();
var n = d.getTime();
var currentTime = TimeSync.serverTime(n);
var startTime = self.data.card.startTime;
var difference = startTime - currentTime;
var hoursDifference = Math.floor(difference/1000/60/60);
difference -= hoursDifference*1000*60*60
var minutesDifference = Math.floor(difference/1000/60);
difference -= minutesDifference*1000*60
var secondsDifference = Math.floor(difference/1000);
/* if ends (make tournament live server side?) */
if (hoursDifference < 0 || minutesDifference < 0 || secondsDifference < 0) {
Meteor.clearInterval(tStartTime);
Session.set("tournamentStartTime", false);
}else{
if (hoursDifference < 10) {hoursDifference = "0"+hoursDifference;}
if (minutesDifference < 10) {minutesDifference = "0"+minutesDifference;}
if (secondsDifference < 10) {secondsDifference = "0"+secondsDifference;}
var formattedTime = hoursDifference + ':' + minutesDifference + ':' + secondsDifference;
Session.set("tournamentStartTime", formattedTime);
}
};
Session.set("tournamentStartTime", '00:00:00');
tournamentStartTime();
var tStartTime = Meteor.setInterval(tournamentStartTime, 1000);
/* Allow new user sign up */
var alreadySignedUp = false;
var usersSignedUp = $('.participant-id')
usersSignedUp.map(function (index, user) {
if ($(user).text().trim() === Meteor.userId()) {
alreadySignedUp = true;
}
});
if (this.data.card.host != Meteor.user().username && !(alreadySignedUp)) {
var openSlots = [];
var allSlots = $('.participant');
allSlots.map(function (index, participant) {
if ($(participant).find('.participant-title').text().trim() === '' && !($(participant).hasClass('loser'))) {
openSlots.push(participant);
}
});
openSlots.map(function (openSlot, index) {
$(openSlot).removeClass('winner').addClass('undetermined');
});
}
/* if theres a registered user */
if (Meteor.userId()) {
}else{
}
}
};
From what i can see there, your rendered function would not work as you expect as the template may render while the loggingIn state is still occuring...
My suggestion would be to use something along the lines of {{#if currentUser}} page here{{/if}} and then put the code you are trying to run in the rendered in a helper inside that currentUser block that way it would only display and be called if there is a logged in user, otherwise it would not show up and you would not need to re-render the page to perform any of that.
Basically once the user has logged in, any helper (other than rendered) that has the Meteor.userId() or Meteor.user() functions being called would re-run automatically, otherwise you could perform login actions inside a Tracker.autorun function if they are global to your app per client.

Categories